package com.rickpan.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rickpan.dto.request.MergeChunksRequest;
import com.rickpan.dto.request.UploadInitRequest;
import com.rickpan.dto.response.ChunkUploadResponse;
import com.rickpan.dto.response.UploadInitResponse;
import com.rickpan.entity.FileInfo;
import com.rickpan.entity.TransferRecord;
import com.rickpan.entity.UploadSession;
import com.rickpan.entity.User;
import com.rickpan.exception.BusinessException;
import com.rickpan.repository.FileInfoRepository;
import com.rickpan.repository.UploadSessionRepository;
import com.rickpan.repository.UserRepository;
import com.rickpan.storage.StorageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.context.ApplicationContext;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

/**
 * 分片上传服务
 * 处理文件分片上传的核心业务逻辑
 */
@Service
@Transactional
public class ChunkUploadService {

    private static final Logger logger = LoggerFactory.getLogger(ChunkUploadService.class);

    // MinIO最小分片大小限制（5MB）
    private static final long MINIO_MIN_PART_SIZE = 5 * 1024 * 1024;
    
    @Autowired
    private UploadSessionRepository uploadSessionRepository;

    @Autowired
    private FileInfoRepository fileInfoRepository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private StorageService storageService;

    @Autowired
    private TransferRecordService transferRecordService;
    
    @Autowired
    private FileManagementService fileManagementService;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private ChunkUploadQueueManager queueManager;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    /**
     * 初始化上传会话
     */
    public UploadInitResponse initUpload(Long userId, UploadInitRequest request) {
        try {
            // 1. 检查用户存储配额
            checkUserStorageQuota(userId, request.getFileSize());

            // 2. 检查文件名冲突并自动重命名
            String finalFileName = resolveFileNameConflict(userId, request.getFileName(), request.getParentId());
            logger.info("文件名冲突检查: 原名={}, 最终名={}", request.getFileName(), finalFileName);

            // 3. 不检查未完成的会话，允许重复上传，每次都创建新会话

            // 4. 创建新的上传会话（使用最终文件名）
            UploadSession session = createUploadSession(userId, request, finalFileName);
            uploadSessionRepository.save(session);

            // 5. 初始化存储（对于 MinIO 多部分上传）
            try {
                storageService.initializeChunkUpload(session.getId(), finalFileName);
                logger.debug("存储初始化成功: uploadId={}", session.getId());
            } catch (Exception e) {
                logger.error("存储初始化失败: uploadId={}", session.getId(), e);
                // 删除已创建的会话
                uploadSessionRepository.delete(session);
                throw new BusinessException("存储初始化失败: " + e.getMessage());
            }

            UploadInitResponse response = createNewUploadResponse(session);
            logger.info("创建新上传响应: uploadId={}, needUpload={}, fileName={}",
                       response.getUploadId(), response.isNeedUpload(), finalFileName);
            return response;

        } catch (Exception e) {
            logger.error("初始化上传会话失败: userId={}, fileName={}",
                        userId, request.getFileName(), e);
            throw new BusinessException("初始化上传失败: " + e.getMessage());
        }
    }
    
    /**
     * 上传分片
     */
    public ChunkUploadResponse uploadChunk(Long userId, String uploadId, Integer chunkIndex,
                                         MultipartFile chunk, String chunkMd5) {
        // 使用队列管理器确保同一uploadId的分片串行处理
        try {
            return queueManager.executeSerially(uploadId, () -> {
            try {
            // 1. 验证上传会话
            UploadSession session = validateUploadSession(userId, uploadId);

            // 2. 验证分片索引
            if (chunkIndex >= session.getTotalChunks()) {
                throw new BusinessException("分片索引超出范围");
            }

            // 3. 验证分片MD5
            String calculatedMd5 = storageService.calculateChunkMD5(chunk);
            if (!calculatedMd5.equals(chunkMd5)) {
                throw new BusinessException("分片校验失败");
            }

            // 4. 检查分片是否已上传
            List<Integer> uploadedChunks = getUploadedChunks(session);
            if (uploadedChunks.contains(chunkIndex)) {
                logger.debug("分片已存在，跳过上传: uploadId={}, chunkIndex={}", uploadId, chunkIndex);
                return createChunkResponse(session, uploadedChunks);
            }

            // 5. 存储分片
            storageService.storeChunk(uploadId, chunkIndex, chunk);

            // 6. 更新上传进度（使用独立事务）
            uploadedChunks.add(chunkIndex);
            try {
                // 通过ApplicationContext调用，确保新事务生效
                ChunkUploadService self = applicationContext.getBean(ChunkUploadService.class);
                self.updateUploadProgress(session, uploadedChunks);
            } catch (Exception e) {
                logger.error("更新上传进度失败: uploadId={}, chunkIndex={}", uploadId, chunkIndex, e);
                throw new BusinessException("更新上传进度失败: " + e.getMessage());
            }

                // 7. 异步更新传输记录（不影响主流程）
                queueManager.executeSeriallyAsync(uploadId, () -> {
                    try {
                        ChunkUploadService self = applicationContext.getBean(ChunkUploadService.class);
                        self.updateTransferProgress(userId, session, uploadedChunks);
                    } catch (Exception e) {
                        logger.warn("更新传输进度失败: uploadId={}, chunkIndex={}", uploadId, chunkIndex, e);
                    }
                });

                logger.debug("分片上传成功: uploadId={}, chunkIndex={}, 总进度: {}/{}",
                           uploadId, chunkIndex, uploadedChunks.size(), session.getTotalChunks());

                return createChunkResponse(session, uploadedChunks);

                } catch (Exception e) {
                    logger.error("上传分片失败: uploadId={}, chunkIndex={}", uploadId, chunkIndex, e);
                    throw new BusinessException("上传分片失败: " + e.getMessage());
                }
            });
        } catch (Exception e) {
            logger.error("队列执行失败: uploadId={}, chunkIndex={}", uploadId, chunkIndex, e);
            throw new BusinessException("上传分片失败: " + e.getMessage());
        }
    }
    
    /**
     * 合并分片
     */
    public FileInfo mergeChunks(Long userId, MergeChunksRequest request) {
        try {
            // 1. 验证上传会话
            UploadSession session = validateUploadSession(userId, request.getUploadId());
            
            // 2. 检查所有分片是否已上传
            List<Integer> uploadedChunks = getUploadedChunks(session);
            if (uploadedChunks.size() != session.getTotalChunks()) {
                throw new BusinessException("分片上传未完成");
            }
            
            // 3. 合并分片文件
            String finalPath;

            // 检查是否为小文件（小于MinIO最小分片大小且只有一个分片）
            if (session.getFileSize() < MINIO_MIN_PART_SIZE && session.getTotalChunks() == 1) {
                logger.info("小文件直接合并: uploadId={}, fileSize={}", session.getId(), session.getFileSize());
                finalPath = storageService.mergeChunks(session.getId(), request.getFileName(), session.getTotalChunks());
            } else {
                // 大文件使用多部分上传合并
                finalPath = storageService.mergeChunks(session.getId(), request.getFileName(), session.getTotalChunks());
            }

            // 4. 验证合并后文件的哈希
            String mergedFileSha256 = storageService.calculateFileSHA256(finalPath);
            if (!mergedFileSha256.equals(session.getFileSha256())) {
                // 删除错误的合并文件
                storageService.deleteFile(finalPath);
                throw new BusinessException("文件合并后校验失败");
            }
            
            // 5. 创建文件记录
            FileInfo fileInfo = createFileRecord(userId, session, finalPath, request.getParentId());
            
            // 6. 更新用户存储使用量 - 直接调用，不使用事务
            updateUserStorageUsage(userId, session.getFileSize());
            
            // 7. 删除上传会话（已完成，不再需要）
            uploadSessionRepository.delete(session);

            // 8. 完成传输记录处理
            try {
                Optional<TransferRecord> transferRecord = transferRecordService.findBySessionId(request.getUploadId());
                if (transferRecord.isPresent()) {
                    TransferRecord record = transferRecord.get();
                    record.setTransferStatus(TransferRecord.TransferStatus.COMPLETED);
                    record.setTransferredBytes(session.getFileSize());
                    record.setProgressPercentage(new java.math.BigDecimal("100.00"));
                    record.setEndTime(LocalDateTime.now());
                    record.setFilePath(finalPath);

                    // 先保存记录
                    transferRecordService.updateTransferRecord(record);

                    // 立即发布完成事件，确保前端能及时收到通知
                    transferRecordService.publishTransferCompletedEvent(record);

                    // 不删除传输记录，保留到传输历史中
                    logger.info("✅ 传输记录已完成并保存到历史: recordId={}, fileName={}, uploadId={}",
                               record.getId(), record.getOriginalName(), request.getUploadId());
                } else {
                    logger.warn("⚠️ 未找到对应的传输记录: uploadId={}", request.getUploadId());
                }
            } catch (Exception e) {
                logger.error("❌ 处理传输记录失败: uploadId={}", request.getUploadId(), e);
            }

            // 9. 清理队列锁
            queueManager.cleanupLock(request.getUploadId());

            logger.info("文件合并成功: fileId={}, fileName={}, size={}",
                       fileInfo.getId(), fileInfo.getOriginalName(), fileInfo.getFileSize());

            return fileInfo;
            
        } catch (Exception e) {
            logger.error("合并分片失败: uploadId={}", request.getUploadId(), e);
            // 清理分片文件
            storageService.cleanupChunks(request.getUploadId());
            throw new BusinessException("合并分片失败: " + e.getMessage());
        }
    }
    
    /**
     * 取消上传
     */
    public void cancelUpload(Long userId, String uploadId) {
        try {
            UploadSession session = validateUploadSession(userId, uploadId);

            // 1. 清理MinIO分片文件
            storageService.cleanupChunks(uploadId);

            // 2. 删除传输记录
            try {
                Optional<TransferRecord> transferRecord = transferRecordService.findBySessionId(uploadId);
                if (transferRecord.isPresent()) {
                    transferRecordService.deleteTransferRecord(transferRecord.get().getId());
                    logger.info("已删除传输记录: recordId={}", transferRecord.get().getId());
                }
            } catch (Exception e) {
                logger.warn("删除传输记录失败: uploadId={}", uploadId, e);
            }

            // 3. 删除上传会话
            uploadSessionRepository.delete(session);

            // 4. 清理队列锁
            queueManager.cleanupLock(uploadId);

            logger.info("上传已取消: uploadId={}, fileName={}", uploadId, session.getFileName());

        } catch (Exception e) {
            logger.error("取消上传失败: uploadId={}", uploadId, e);
            throw new BusinessException("取消上传失败: " + e.getMessage());
        }
    }
    
    /**
     * 获取上传进度
     */
    public ChunkUploadResponse getUploadProgress(Long userId, String uploadId) {
        try {
            UploadSession session = validateUploadSession(userId, uploadId);
            List<Integer> uploadedChunks = getUploadedChunks(session);
            
            return createChunkResponse(session, uploadedChunks);
            
        } catch (Exception e) {
            logger.error("获取上传进度失败: uploadId={}", uploadId, e);
            throw new BusinessException("获取上传进度失败: " + e.getMessage());
        }
    }
    
    // 私有辅助方法
    
    private void checkUserStorageQuota(Long userId, Long fileSize) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new BusinessException("用户不存在"));
        
        if (user.getStorageUsed() + fileSize > user.getStorageQuota()) {
            throw new BusinessException("存储空间不足");
        }
    }
    
    /**
     * 解决文件名冲突，自动重命名
     */
    private String resolveFileNameConflict(Long userId, String originalFileName, Long parentId) {
        String fileName = originalFileName;
        int counter = 1;

        // 检查文件名是否已存在
        while (fileInfoRepository.existsByOwnerIdAndParentIdAndOriginalNameAndIsDeletedFalse(
                userId, parentId, fileName)) {

            // 生成新的文件名：文件名(1).扩展名
            String nameWithoutExt;
            String extension = "";

            int lastDotIndex = originalFileName.lastIndexOf('.');
            if (lastDotIndex > 0) {
                nameWithoutExt = originalFileName.substring(0, lastDotIndex);
                extension = originalFileName.substring(lastDotIndex);
            } else {
                nameWithoutExt = originalFileName;
            }

            fileName = nameWithoutExt + "(" + counter + ")" + extension;
            counter++;

            // 防止无限循环
            if (counter > 1000) {
                throw new BusinessException("文件名冲突解决失败，请手动重命名文件");
            }
        }

        return fileName;
    }
    
    private UploadSession createUploadSession(Long userId, UploadInitRequest request, String finalFileName) {
        String uploadId = UUID.randomUUID().toString();
        int totalChunks = (int) Math.ceil((double) request.getFileSize() / request.getChunkSize());

        return new UploadSession(
                uploadId,
                userId,
                finalFileName,  // 使用解决冲突后的文件名
                request.getFileSize(),
                request.getFileSha256(),
                request.getChunkSize(),
                totalChunks
        );
    }
    
    private UploadSession validateUploadSession(Long userId, String uploadId) {
        UploadSession session = uploadSessionRepository.findById(uploadId)
                .orElseThrow(() -> new BusinessException("上传会话不存在"));
        
        if (!session.getUserId().equals(userId)) {
            throw new BusinessException("无权访问此上传会话");
        }
        
        if (!session.canUpload()) {
            throw new BusinessException("上传会话已过期或无效");
        }
        
        return session;
    }
    
    private List<Integer> getUploadedChunks(UploadSession session) {
        try {
            if (session.getUploadedChunks() == null || session.getUploadedChunks().equals("[]")) {
                return new ArrayList<>();
            }
            return objectMapper.readValue(session.getUploadedChunks(), new TypeReference<List<Integer>>() {});
        } catch (Exception e) {
            logger.warn("解析已上传分片失败: uploadId={}", session.getId(), e);
            return new ArrayList<>();
        }
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateUploadProgress(UploadSession session, List<Integer> uploadedChunks) {
        try {
            String chunksJson = objectMapper.writeValueAsString(uploadedChunks);
            session.setUploadedChunks(chunksJson);
            uploadSessionRepository.save(session);
            logger.debug("上传进度更新成功: uploadId={}, 已上传分片数: {}",
                        session.getId(), uploadedChunks.size());
        } catch (Exception e) {
            logger.error("更新上传进度失败: uploadId={}", session.getId(), e);
            throw new BusinessException("更新上传进度失败");
        }
    }
    
    private FileInfo createFileRecord(Long userId, UploadSession session, String filePath, Long parentId) {
        FileInfo fileInfo = new FileInfo();
        fileInfo.setFileName(UUID.randomUUID().toString() + storageService.getFileExtension(session.getFileName()));
        fileInfo.setOriginalName(session.getFileName());
        fileInfo.setFilePath(filePath);
        fileInfo.setFileSize(session.getFileSize());
        fileInfo.setFileSha256(session.getFileSha256());
        // 设置fileHash字段，使用SHA256值（如果为空则使用文件路径的哈希）
        fileInfo.setFileHash(session.getFileSha256() != null ? session.getFileSha256() :
                           Integer.toHexString(filePath.hashCode()));
        // 设置MIME类型 - 根据文件扩展名推断
        fileInfo.setMimeType(getMimeTypeFromFileName(session.getFileName()));
        fileInfo.setOwnerId(userId);
        fileInfo.setParentId(parentId);
        fileInfo.setIsDirectory(false);
        fileInfo.setIsDeleted(false);
        fileInfo.setCreatedAt(LocalDateTime.now());
        fileInfo.setUpdatedAt(LocalDateTime.now());

        return fileInfoRepository.save(fileInfo);
    }

    /**
     * 根据文件名推断MIME类型
     */
    private String getMimeTypeFromFileName(String fileName) {
        if (fileName == null) return "application/octet-stream";

        String extension = fileName.toLowerCase();
        int lastDotIndex = extension.lastIndexOf('.');
        if (lastDotIndex > 0) {
            extension = extension.substring(lastDotIndex + 1);
        }

        // 常见文件类型的MIME类型映射
        switch (extension) {
            // 图片
            case "jpg":
            case "jpeg":
                return "image/jpeg";
            case "png":
                return "image/png";
            case "gif":
                return "image/gif";
            case "bmp":
                return "image/bmp";
            case "webp":
                return "image/webp";
            case "svg":
                return "image/svg+xml";

            // 文档
            case "pdf":
                return "application/pdf";
            case "doc":
                return "application/msword";
            case "docx":
                return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
            case "xls":
                return "application/vnd.ms-excel";
            case "xlsx":
                return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
            case "ppt":
                return "application/vnd.ms-powerpoint";
            case "pptx":
                return "application/vnd.openxmlformats-officedocument.presentationml.presentation";

            // 文本
            case "txt":
                return "text/plain";
            case "html":
            case "htm":
                return "text/html";
            case "css":
                return "text/css";
            case "js":
                return "application/javascript";
            case "json":
                return "application/json";
            case "xml":
                return "application/xml";
            case "csv":
                return "text/csv";

            // 视频
            case "mp4":
                return "video/mp4";
            case "avi":
                return "video/x-msvideo";
            case "mov":
                return "video/quicktime";
            case "wmv":
                return "video/x-ms-wmv";
            case "flv":
                return "video/x-flv";
            case "webm":
                return "video/webm";
            case "mkv":
                return "video/x-matroska";

            // 音频
            case "mp3":
                return "audio/mpeg";
            case "wav":
                return "audio/wav";
            case "flac":
                return "audio/flac";
            case "aac":
                return "audio/aac";
            case "ogg":
                return "audio/ogg";

            // 压缩文件
            case "zip":
                return "application/zip";
            case "rar":
                return "application/x-rar-compressed";
            case "7z":
                return "application/x-7z-compressed";
            case "tar":
                return "application/x-tar";
            case "gz":
                return "application/gzip";

            default:
                return "application/octet-stream";
        }
    }

    private void updateUserStorageUsage(Long userId, Long fileSize) {
        // 简化处理：直接更新，使用重试机制处理并发
        int maxRetries = 3;
        Exception lastException = null;

        for (int i = 0; i < maxRetries; i++) {
            try {
                int updatedRows = userRepository.updateStorageUsed(userId, fileSize);
                if (updatedRows > 0) {
                    logger.debug("存储使用量更新成功: userId={}, fileSize={}, retry={}", userId, fileSize, i);
                    return; // 成功
                }

                // 检查用户是否存在
                if (!userRepository.existsById(userId)) {
                    throw new BusinessException("用户不存在: " + userId);
                }

                // 用户存在但更新失败，可能是并发问题，继续重试
                lastException = new RuntimeException("更新行数为0，可能存在并发冲突");

                if (i < maxRetries - 1) {
                    try {
                        Thread.sleep(50 * (i + 1)); // 50ms, 100ms, 150ms
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new BusinessException("更新被中断");
                    }
                }

            } catch (Exception e) {
                lastException = e;
                String errorMsg = e.getMessage() != null ? e.getMessage().toLowerCase() : "";

                if (errorMsg.contains("deadlock") || errorMsg.contains("lock wait timeout")) {
                    if (i < maxRetries - 1) {
                        logger.warn("存储使用量更新遇到锁冲突，重试第{}次: userId={}", i + 1, userId);
                        try {
                            Thread.sleep(100 * (i + 1));
                        } catch (InterruptedException ie) {
                            Thread.currentThread().interrupt();
                            throw new BusinessException("更新被中断");
                        }
                    } else {
                        break;
                    }
                } else {
                    break;
                }
            }
        }

        // 所有重试都失败，记录警告但不阻止文件上传
        String errorMsg = lastException != null ? lastException.getMessage() : "未知错误";
        logger.warn("存储使用量更新失败，但文件上传继续: userId={}, fileSize={}, error={}",
                   userId, fileSize, errorMsg);
    }
    
    // 移除了文件去重相关的方法，改为支持重复上传和自动重命名
    
    private UploadInitResponse createNewUploadResponse(UploadSession session) {
        UploadInitResponse response = new UploadInitResponse();
        response.setUploadId(session.getId());
        response.setNeedUpload(true);
        response.setUploadedChunks(new ArrayList<>());
        response.setChunkSize(session.getChunkSize());
        response.setTotalChunks(session.getTotalChunks());
        
        return response;
    }
    
    private ChunkUploadResponse createChunkResponse(UploadSession session, List<Integer> uploadedChunks) {
        long uploadedSize = (long) uploadedChunks.size() * session.getChunkSize();
        int progress = (int) Math.round((double) uploadedSize / session.getFileSize() * 100);

        ChunkUploadResponse response = new ChunkUploadResponse();
        response.setSuccess(true);
        response.setMessage("分片上传成功");
        response.setUploadedSize(uploadedSize);
        response.setProgress(Math.min(progress, 100));

        return response;
    }

    /**
     * 更新传输进度并发布Socket.IO事件（使用新事务）
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateTransferProgress(Long userId, UploadSession session, List<Integer> uploadedChunks) {
        try {
            // 使用UploadSession的标准进度计算方法
            long uploadedSize = session.calculateUploadedBytes();
            int progress = session.calculateProgress();

            // 查找或创建传输记录
            TransferRecord transferRecord = transferRecordService.findOrCreateTransferRecord(
                userId,
                session.getId(),
                session.getFileName(),
                session.getFileSize()
            );

            // 计算传输速度（简化版本，实际应该基于时间差计算）
            long speed = 0L; // TODO: 实现真实的速度计算

            // 更新传输进度（这会自动发布Socket.IO事件）
            transferRecordService.updateTransferProgress(transferRecord.getId(), uploadedSize, speed);

            logger.debug("更新传输进度: uploadId={}, progress={}%, uploadedSize={}",
                        session.getId(), progress, uploadedSize);

        } catch (Exception e) {
            logger.error("更新传输进度失败: uploadId={}", session.getId(), e);
            throw e; // 在新事务中可以抛出异常
        }
    }


}
